"""
符号索引和引用关系构建器
构建项目级的符号索引、引用关系和依赖图
"""

import os
from pathlib import Path
from typing import Dict, List, Set, Tuple, Optional, Any
from collections import defaultdict
import json

from tree_sitter_config import Symbol, FileAnalysis, Position, Range
from python_parser import PythonParser


class SymbolIndex:
    """符号索引"""
    
    def __init__(self):
        # 按名称索引符号
        self.symbols_by_name: Dict[str, List[Symbol]] = defaultdict(list)
        # 按文件索引符号
        self.symbols_by_file: Dict[str, List[Symbol]] = defaultdict(list)
        # 按类型索引符号
        self.symbols_by_type: Dict[str, List[Symbol]] = defaultdict(list)
        # 按作用域索引符号
        self.symbols_by_scope: Dict[str, List[Symbol]] = defaultdict(list)
        # 全局符号表
        self.all_symbols: List[Symbol] = []
        
    def add_symbol(self, symbol: Symbol):
        """添加符号到索引"""
        self.all_symbols.append(symbol)
        self.symbols_by_name[symbol.name].append(symbol)
        self.symbols_by_file[symbol.file_path].append(symbol)
        self.symbols_by_type[symbol.type].append(symbol)
        self.symbols_by_scope[symbol.scope].append(symbol)
        
        # 递归添加子符号
        for child in symbol.children:
            self.add_symbol(child)
    
    def find_symbols(self, name: str, symbol_type: Optional[str] = None, 
                    file_path: Optional[str] = None) -> List[Symbol]:
        """查找符号"""
        candidates = self.symbols_by_name.get(name, [])
        
        if symbol_type:
            candidates = [s for s in candidates if s.type == symbol_type]
        
        if file_path:
            candidates = [s for s in candidates if s.file_path == file_path]
        
        return candidates
    
    def get_symbols_in_file(self, file_path: str) -> List[Symbol]:
        """获取文件中的所有符号"""
        return self.symbols_by_file.get(file_path, [])
    
    def get_symbols_by_type(self, symbol_type: str) -> List[Symbol]:
        """按类型获取符号"""
        return self.symbols_by_type.get(symbol_type, [])
    
    def get_statistics(self) -> Dict[str, Any]:
        """获取索引统计信息"""
        return {
            "total_symbols": len(self.all_symbols),
            "symbols_by_type": {t: len(symbols) for t, symbols in self.symbols_by_type.items()},
            "files_count": len(self.symbols_by_file),
            "scopes_count": len(self.symbols_by_scope)
        }


class ReferenceResolver:
    """引用解析器"""
    
    def __init__(self, symbol_index: SymbolIndex, parser: PythonParser):
        self.symbol_index = symbol_index
        self.parser = parser
        self.references: Dict[str, List[Tuple[str, Range]]] = defaultdict(list)
        self.call_graph: Dict[str, Set[str]] = defaultdict(set)
        self.import_graph: Dict[str, Set[str]] = defaultdict(set)
    
    def resolve_references(self, file_analyses: Dict[str, FileAnalysis]):
        """解析所有引用关系"""
        print("正在解析引用关系...")
        
        for file_path, analysis in file_analyses.items():
            if analysis.ast:
                self._resolve_file_references(file_path, analysis)
        
        self._build_call_graph(file_analyses)
        self._build_import_graph(file_analyses)
    
    def _resolve_file_references(self, file_path: str, analysis: FileAnalysis):
        """解析单个文件的引用"""
        try:
            with open(file_path, 'rb') as f:
                source_code = f.read()
            
            # 获取所有符号名称
            symbol_names = set()
            for symbol in analysis.symbols:
                symbol_names.add(symbol.name)
                # 添加类方法名称
                for child in symbol.children:
                    symbol_names.add(child.name)
            
            # 查找引用
            for symbol_name in symbol_names:
                references = self.parser.extract_references(
                    analysis.ast, source_code, symbol_name
                )
                for ref_range in references:
                    self.references[symbol_name].append((file_path, ref_range))
                    
                    # 更新符号的引用列表
                    symbols = self.symbol_index.find_symbols(symbol_name)
                    for symbol in symbols:
                        if ref_range not in symbol.references:
                            symbol.references.append(ref_range)
            
        except Exception as e:
            print(f"解析文件 {file_path} 的引用时出错: {e}")
    
    def _build_call_graph(self, file_analyses: Dict[str, FileAnalysis]):
        """构建调用图"""
        for file_path, analysis in file_analyses.items():
            if analysis.ast:
                try:
                    with open(file_path, 'rb') as f:
                        source_code = f.read()
                    
                    # 获取函数调用
                    calls = self.parser.extract_function_calls(analysis.ast, source_code)
                    
                    # 获取当前文件的函数
                    file_functions = [s for s in analysis.symbols if s.type == "function"]
                    
                    for func_symbol in file_functions:
                        caller = f"{file_path}::{func_symbol.name}"
                        
                        for call_name, call_range in calls:
                            # 检查调用是否在当前函数内
                            if self._is_range_inside(call_range, func_symbol.range):
                                # 查找被调用的函数
                                called_symbols = self.symbol_index.find_symbols(call_name, "function")
                                for called_symbol in called_symbols:
                                    callee = f"{called_symbol.file_path}::{called_symbol.name}"
                                    self.call_graph[caller].add(callee)
                
                except Exception as e:
                    print(f"构建调用图时处理文件 {file_path} 出错: {e}")
    
    def _build_import_graph(self, file_analyses: Dict[str, FileAnalysis]):
        """构建导入依赖图"""
        for file_path, analysis in file_analyses.items():
            imports = set()
            for import_symbol in analysis.imports:
                imports.add(import_symbol.name)
            self.import_graph[file_path] = imports
    
    def _is_range_inside(self, inner_range: Range, outer_range: Range) -> bool:
        """检查一个范围是否在另一个范围内"""
        return (
            (inner_range.start.line > outer_range.start.line or 
             (inner_range.start.line == outer_range.start.line and 
              inner_range.start.column >= outer_range.start.column)) and
            (inner_range.end.line < outer_range.end.line or 
             (inner_range.end.line == outer_range.end.line and 
              inner_range.end.column <= outer_range.end.column))
        )
    
    def get_references(self, symbol_name: str) -> List[Tuple[str, Range]]:
        """获取符号的所有引用"""
        return self.references.get(symbol_name, [])
    
    def get_callers(self, function_identifier: str) -> Set[str]:
        """获取调用指定函数的所有函数"""
        callers = set()
        for caller, callees in self.call_graph.items():
            if function_identifier in callees:
                callers.add(caller)
        return callers
    
    def get_callees(self, function_identifier: str) -> Set[str]:
        """获取指定函数调用的所有函数"""
        return self.call_graph.get(function_identifier, set())
    
    def get_file_dependencies(self, file_path: str) -> Set[str]:
        """获取文件的导入依赖"""
        return self.import_graph.get(file_path, set())
    
    def find_circular_dependencies(self) -> List[List[str]]:
        """查找循环依赖"""
        # 简化的循环依赖检测
        visited = set()
        rec_stack = set()
        cycles = []
        
        def dfs(file_path, path):
            if file_path in rec_stack:
                # 找到循环
                cycle_start = path.index(file_path)
                cycles.append(path[cycle_start:] + [file_path])
                return
            
            if file_path in visited:
                return
            
            visited.add(file_path)
            rec_stack.add(file_path)
            
            # 这里需要实际的文件依赖关系，简化处理
            for dep in self.import_graph.get(file_path, set()):
                dfs(dep, path + [dep])
            
            rec_stack.remove(file_path)
        
        for file_path in self.import_graph.keys():
            if file_path not in visited:
                dfs(file_path, [file_path])
        
        return cycles


class CodeMetrics:
    """代码度量计算器"""
    
    def __init__(self, symbol_index: SymbolIndex, reference_resolver: ReferenceResolver):
        self.symbol_index = symbol_index
        self.reference_resolver = reference_resolver
    
    def calculate_complexity_metrics(self) -> Dict[str, Any]:
        """计算复杂度指标"""
        metrics = {
            "cyclomatic_complexity": {},
            "coupling_metrics": {},
            "cohesion_metrics": {},
            "size_metrics": {}
        }
        
        # 计算文件级指标
        for file_path in self.symbol_index.symbols_by_file.keys():
            file_symbols = self.symbol_index.get_symbols_in_file(file_path)
            
            # 计算文件大小指标
            functions = [s for s in file_symbols if s.type == "function"]
            classes = [s for s in file_symbols if s.type == "class"]
            
            metrics["size_metrics"][file_path] = {
                "functions_count": len(functions),
                "classes_count": len(classes),
                "total_symbols": len(file_symbols)
            }
            
            # 计算耦合度
            dependencies = self.reference_resolver.get_file_dependencies(file_path)
            metrics["coupling_metrics"][file_path] = {
                "afferent_coupling": len(dependencies),
                "dependencies": list(dependencies)
            }
        
        return metrics
    
    def get_hotspots(self) -> List[Dict[str, Any]]:
        """识别代码热点（高复杂度/高耦合的区域）"""
        hotspots = []
        
        # 查找引用次数最多的符号
        reference_counts = {}
        for symbol_name, refs in self.reference_resolver.references.items():
            reference_counts[symbol_name] = len(refs)
        
        # 排序并取前10个
        top_referenced = sorted(reference_counts.items(), 
                               key=lambda x: x[1], reverse=True)[:10]
        
        for symbol_name, ref_count in top_referenced:
            symbols = self.symbol_index.find_symbols(symbol_name)
            for symbol in symbols:
                hotspots.append({
                    "symbol": symbol.name,
                    "type": symbol.type,
                    "file": symbol.file_path,
                    "reference_count": ref_count,
                    "scope": symbol.scope
                })
        
        return hotspots